package nachos.threads;
import nachos.ag.BoatGrader;

import nachos.machine.Lib;

public class Boat
{
    static BoatGrader bg;

    // added by Tom for synch-ing
    private static Lock boatlock;
    private static Condition2 cbl_oahu;
    private static Condition2 cbl_molokai;
    private static Condition2 cbl_passenger;
    private static Communicator talker;

    private static final int ending = 1;

    private static final int locOahu    = 0;
    private static final int locMolokai = 1;

    private static final int noPilot = 0;
    private static final int pChild  = 1;
    private static final int pAdult  = 2;

    private static int     boatPilot;
    private static boolean boatPassenger;
    private static int     boatLocation;
    
    // should only be accessible when on Molokai
    private static int childrenOnMolokai;
    private static int adultsOnMolokai;
    
    // should only be accessible when on Oahu
    private static int childrenOnOahu;
    private static int adultsOnOahu;

    // the "keep going" variable
    private static boolean allPeopleOnMolokai;

    public static void selfTest()
    {
	BoatGrader b = new BoatGrader();

	// setup debugging flags
	// b = begin
	// a = adult
	// c = child
	Lib.enableDebugFlags("");
	
	//System.out.println("\n ***Testing Boats with only 2 children***");
	//begin(0, 2, b);

	//System.out.println("\n ***Testing Boats with 2 children, 1 adult***");
  	//begin(1, 2, b);

  	//System.out.println("\n ***Testing Boats with 3 children, 3 adults***");
  	//begin(3, 3, b);

	System.out.println("\n ***Testing Boats with 10 children, 11 adults***");
	begin( 11, 10, b );
    }

    public static void begin( int adults, int children, BoatGrader b )
    {
	int totalPeople;

	// Store the externally generated autograder in a class
	// variable to be accessible by children.
	bg = b;

	// Instantiate global variables here
	boatlock = new Lock();
	cbl_oahu = new Condition2( boatlock );
	cbl_molokai = new Condition2( boatlock );
	cbl_passenger = new Condition2( boatlock );
	talker = new Communicator();

	totalPeople   = children + adults;
	allPeopleOnMolokai = false;
	childrenOnOahu = 0;
	adultsOnOahu   = 0;
	boatPilot = noPilot;
	boatLocation = locOahu;
	boatPassenger = false;
	
	// Create threads here. See section 3.4 of the Nachos for Java
	// Walkthrough linked from the projects page.

	// this prevents the children from doing anything until all threads
	// are created. 
	boatlock.acquire(); 

	// create the children kthreads
	for ( int i = 0 ; i < children; ++i )
	    {
		Runnable r = new Runnable()
		    {
			public void run()
			{
			    ChildItinerary();
			}
		    };

		KThread kt = new KThread( r );
		kt.setName( "Child " + (i+1) );
		kt.fork();
	    } // end of for i = 0 ... totalChildren

	// create the children kthreads
	for ( int i = 0 ; i < adults; ++i )
	    {
		Runnable r = new Runnable()
		    {
			public void run()
			{
			    AdultItinerary();
			}
		    };

		KThread kt = new KThread( r );
		kt.setName( "Adult " + (i+1) );
		kt.fork();
	    } // end of for i = 0... totalAdults

	boatlock.release();
	Lib.debug('b', "\t\tturning loose the people");
	while ( !allPeopleOnMolokai )
	    {
		int val = talker.listen();

		Lib.debug('b', "\t\t\t\t\t\t\t\t\t\t\t\tsomeone did something " + val );


		Lib.debug('b', "\t\t\t\t\t\t\t\t\t\t\t\tchildrenOnMolokai = " + childrenOnMolokai );
		Lib.debug('b', "\t\t\t\t\t\t\t\t\t\t\t\tadultsOnMolokai   = " + adultsOnMolokai );

		if ( totalPeople <= (childrenOnMolokai + adultsOnMolokai) )
		     { // <= the combination shouldn't matter but just to be safe
			 allPeopleOnMolokai = true;	
			 break;
		     }
	    }

	System.out.println("Everyone has made it to Molokai!");
    }

    static void AdultItinerary()
    {
	int location = locOahu;
	boolean flag = false; // possible ending flag

	boatlock.acquire();
	++adultsOnOahu;
	boatlock.release();
	
	while ( !allPeopleOnMolokai  )
	    {
		if ( location == locOahu )
		    {
			// lock the boat
			boatlock.acquire();
			
			Lib.debug('a', "\t\t\t\t\t\tThread <" + KThread.currentThread().getName() + ", Oahu> has the lock on Oahu (adult).");
			// wait for it to be onOahu
			while ( boatLocation != locOahu || boatPilot != noPilot )
			    {
				// sleep waiting for boat to oahu
				Lib.debug('a', "\t\t\t\t\t\tThread <" + KThread.currentThread().getName() + ", Oahu> is sleeping.");
				cbl_oahu.sleep();
			    }

			if ( boatPilot == noPilot )
			    {

				// before i leave is there anyone left on the island
				if ( adultsOnOahu == 1 && childrenOnOahu == 0 )
				    flag = true;
				
				// travel to Molokai
				location = locMolokai;
				boatLocation = locMolokai;
				++adultsOnMolokai;
				--adultsOnOahu;
				bg.AdultRowToMolokai();

				Lib.debug('a', "\t\t\t\t\t\tThread <" + KThread.currentThread().getName() + ", Oahu> has rowed to Molokai.");

				if ( flag )
				    talker.speak( ending );

				// wake up anyone waiting on the boat to get to molokai
				cbl_molokai.wakeAll();

				flag = false;
				// decrease thread priority ??
				ThreadedKernel.scheduler.decreasePriority();
			    }

			boatlock.release();

		    } // end of location == locOahu
		 else // if location == locMolokai
		     {
			 boatlock.acquire();

			 // wait for the boat to be on Molokai and wait until there 
			 // are no children as well
			 Lib.debug('a', "\t\t\t\t\tThread <" + KThread.currentThread().getName() + ", Molokai> has acquired lock on Molokai.");
			 while ( boatLocation != locMolokai || childrenOnMolokai > 0 )
			     {
				 Lib.debug('a', "\t\t\t\t\tThread <" + KThread.currentThread().getName() + ", Molokai> is sleeping on Molokai (" + childrenOnMolokai + ").");
				 cbl_molokai.sleep();
			     }

			 // travel to oahu
			 location = locOahu;
			 boatLocation = locOahu;
			 --adultsOnMolokai;
			 ++adultsOnOahu;
			 bg.AdultRowToOahu();

			 Lib.debug('a', "\t\t\t\t\tThread <" + KThread.currentThread().getName() + ", Molokai> has rowed to Oahu.");

			 // wake up anyone waiting on the boat to get to oahu
			 cbl_oahu.wakeAll();

			 ThreadedKernel.scheduler.increasePriority();

			 boatlock.release();
		     } // end of location == locMolokai
		
		KThread.yield();
	     } // end of while 1
	Lib.debug('a', "\t\t\t\t\tThread <" + KThread.currentThread().getName() + "> has terminated.");
    }

    static void ChildItinerary()
    {
	int location = locOahu;
	boolean flag = false;

	boatlock.acquire();
	++childrenOnOahu;
	boatlock.release();

	
	while ( !allPeopleOnMolokai )
	    {
		if ( location == locOahu )
		    {
			boatlock.acquire();
			Lib.debug('c', "\t\tThread <" + KThread.currentThread().getName() + ", Oahu> has gotten the lock ");
			while ( boatLocation != locOahu || ( boatPilot != noPilot && boatPassenger ) )
			    {
				// wait for boat
				Lib.debug('c', "\t\tThread <" + KThread.currentThread().getName() + ", Oahu> sleeping.");
				Lib.debug('c', "\t\t   location is      " + boatLocation );
				Lib.debug('c', "\t\t   boatPilot is     " + boatPilot );
				Lib.debug('c', "\t\t   boatPassenger is " + boatPassenger );
				cbl_oahu.sleep();
			    }

			if ( boatPilot == noPilot )
			    {
				// set myself as pilot
				boatPilot = pChild;
			        Lib.debug('c', "\t\tThread <" + KThread.currentThread().getName() + ", Oahu> is now the pilot.");

				// wait for boat to get a passenger (if there are children on Oahu)
				//    children > 1 because at least one is this thread!
				while ( childrenOnOahu > 1 && !boatPassenger )
				    {
					Lib.debug('c', "\t\tThread <" + KThread.currentThread().getName() + ", Oahu> is waiting on a passenger (childrenOnOahu = " + childrenOnOahu + ").");
					cbl_oahu.wakeAll();
					cbl_passenger.sleep();
				    }

				// am i one of the last ones
				if ( childrenOnOahu == 1 && adultsOnOahu == 0)
				    flag = true;

				location = locMolokai;
				boatLocation = locMolokai;
				++childrenOnMolokai;
				--childrenOnOahu;
				bg.ChildRowToMolokai();
				boatPilot = noPilot;

				if ( flag )
				    talker.speak( ending );
				
				if ( boatPassenger ) // need to wake him up
				    cbl_passenger.wake();
				else 
				    cbl_molokai.wakeAll();

				    
			    }
			else if ( boatPassenger == false )
			    {
				boatPassenger = true;
				Lib.debug('c', "\t\tThread <" + KThread.currentThread().getName() + ", Oahu> is the passenger.");

				cbl_passenger.wake();

				if ( childrenOnOahu == 2 && adultsOnOahu == 0 ) // me and the pilot 
				    flag = true;

				// wait for boat to get to molokai
				while ( boatLocation == locOahu )
				    {
					Lib.debug('c', "\t\tThread <" + KThread.currentThread().getName() + ", Oahu> is the passenger and waiting to get to Molokai.");
					cbl_passenger.sleep();
					//cbl_molokai.sleep();
				    }

				// unload at Molokai
				Lib.debug('c', "\t\tThread <" + KThread.currentThread().getName() + ", Oahu> is unloading at Molokai");
				location = locMolokai;
				++childrenOnMolokai;
				--childrenOnOahu;
				bg.ChildRideToMolokai();
				boatPassenger = false;
				cbl_molokai.wakeAll();

				if ( flag ) 
				    talker.speak( ending );

			    }
			else
			    {
				// should i even be able to get here?
				Lib.debug('c', "\t\tThread <" + KThread.currentThread().getName() + ", Oahu> Shouldn't be here!!!!!");
			    }
	
			// increment my priority on Molokai
			ThreadedKernel.scheduler.increasePriority();		

			boatlock.release();
		    }
		else // if location == locMolokai
		    {
			Lib.debug('c', "Thread <" + KThread.currentThread().getName() + ", Molokai> has acquired lock on Molokai.");
			boatlock.acquire();
			
			while ( boatLocation != locMolokai || boatPilot != noPilot || boatPassenger )
			    {
				Lib.debug('c', "Thread <" + KThread.currentThread().getName() + ", Molokai> is sleeping on Molokai." );
				cbl_molokai.sleep();
			    }
			
			// travel solo back to oahu
			location = locOahu;
			boatLocation = locOahu;
			--childrenOnMolokai;
			++childrenOnOahu;
			bg.ChildRowToOahu();			

			cbl_oahu.wake();
			Lib.debug('c', "Thread <" + KThread.currentThread().getName() + ", Molokai> has arrived on Oahu");

			// decrement my priority on Oahu
			ThreadedKernel.scheduler.decreasePriority();

			boatlock.release();
		    }

		// force a yield at the end of processing to an island.
		KThread.yield();

	    }  // end of while 1

	Lib.debug('c', "Thread <" + KThread.currentThread().getName() + "> has terminated.");
    }

    static void SampleItinerary()
    {
	// Please note that this isn't a valid solution (you can't fit
	// all of them on the boat). Please also note that you may not
	// have a single thread calculate a solution and then just play
	// it back at the autograder -- you will be caught.
	System.out.println("\n ***Everyone piles on the boat and goes to Molokai***");
	bg.AdultRowToMolokai();
	bg.ChildRideToMolokai();
	bg.AdultRideToMolokai();
	bg.ChildRideToMolokai();
    }
    
}
